#include <cpu.h>
#include <isa.h>
#include <memory/vaddr.h>
#include <memory/paddr.h>
#include <verilated_vcd_c.h>
// #include <verilated_fst_c.h>
// #include "VRISCV_BOARD___024root.h"
#include "VysyxSoCFull___024root.h"
#include <svdpi.h>
#include <sdb/watchpoint.h>
#include <trace/itrace.h>
#include <trace/ftrace.h>
#include <trace/mtrace.h>
#include <trace/isa-difftest.h>


vluint64_t sim_time = 0;
CPU_state cpu_data = {};
uint64_t inst_cnt = 0;
// extern VRISCV_BOARD* cpu;
extern VysyxSoCFull* cpu;
extern mode launch_mode;

const char *regs[] = {
	"$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
	"s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
	"a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
	"s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
};

/* STATUS */

void set_cpu_status(int state, vaddr_t pc, int halt_ret) {
	cpu_status.state = state;
	cpu_status.halt_pc = pc;
	cpu_status.halt_ret = halt_ret;
}

extern "C" void halt(svBit is_halt){
	//NPCTRAP(cpu->pc,10号寄存器($a0)的内容);
	if(is_halt){
		NPCTRAP(cpu_data.pc,gpr(10));
	}
	return;
}
extern VerilatedVcdC* tfp;
void cpu_terminate(){
	if(cpu_status.state == ABORT){//ABORT
        IFDEF(CONFIG_ITRACE_COND,printf("\n");buffer_disp());
        IFDEF(CONFIG_MTRACE_COND,printf("\n");mem_buffer_disp());
		printf("\nNPC EXIT: \033[0m\033[1;31mABORT\033[0m at pc = %#010x\n\n",cpu_status.halt_pc);
    }else if(cpu_status.halt_ret == 0){//GOOD
		printf("\nNPC EXIT: \033[0m\033[1;32mHIT GOOD TRAP\033[0m at pc = %#010x\n\n",cpu_status.halt_pc);
	}else{//BAD or unexpected situation	
		IFDEF(CONFIG_ITRACE_COND,printf("\n");buffer_disp());
		IFDEF(CONFIG_MTRACE_COND,printf("\n");mem_buffer_disp());
		printf("\nNPC EXIT: \033[0m\033[1;31mHIT BAD TRAP\033[0m at pc = %#010x\n\n",cpu_status.halt_pc);
	}
	IFDEF(CONFIG_TRACE,tfp->close());
	cpu->final();
	delete cpu;
	
}


/* DATA */
static void single_inst_debug();
bool need_debug = false;

extern "C" void get_cpu(svBit en,int pc,int dnpc,int inst,svBit load_en,svBit store_en,int ls_addr){
    if(cpu_data.pc!=pc) need_debug = true;
    cpu_data.pc = pc;
    cpu_data.dnpc = dnpc;
    cpu_data.inst = inst;

    cpu_status.load_en = load_en;
    cpu_status.store_en = store_en;
    cpu_status.is_device = (load_en || store_en) && in_dmem(ls_addr);
    IFDEF(CONFIG_DIFFTEST_COND,if(cpu_status.is_device) {difftest_skip_ref();});
}

// extern "C" void get_cpu_inst(int inst){
// 	cpu_data.inst = inst;
// }

// extern "C" void get_cpu_ls(svBit load_en,svBit store_en,int ls_addr){
//     cpu_status.load_en = load_en;
//     cpu_status.store_en = store_en;
//     cpu_status.is_device = ls_addr >= DEVICE_BASE;
//     IFDEF(CONFIG_DIFFTEST_COND,if(cpu_status.is_device) {difftest_skip_ref();printf("debug=%x\n",cpu_data.pc);});
// }

// void get_cpu_reg(){
// 	VlUnpacked<IData,32> rf_struct = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__rf1__DOT__regs;
// 	gpr(0) = 0;
// 	for(int i=1;i<REG_NUM;i++) gpr(i) = rf_struct.m_storage[i];

//     cpu_data.mtvec = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mtvec;
//     cpu_data.mepc = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mepc;
//     cpu_data.mcause = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mcause;

//     cpu_data.mtval = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mtval;
//     cpu_data.mstatus = cpu->rootp->RISCV_BOARD__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mstatus;

// }

void get_cpu_reg(){
	VlUnpacked<IData,32> rf_struct = cpu->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__cpu__DOT__id_stage__DOT__rf1__DOT__regs;
	gpr(0) = 0;
	for(int i=1;i<REG_NUM;i++) gpr(i) = rf_struct.m_storage[i];

    cpu_data.mtvec = cpu->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mtvec;
    cpu_data.mepc = cpu->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mepc;
    cpu_data.mcause = cpu->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mcause;

    // cpu_data.mtval = cpu->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mtval;
    // cpu_data.mstatus = cpu->rootp->ysyxSoCFull__DOT__asic__DOT__cpu__DOT__cpu__DOT__cpu__DOT__id_stage__DOT__rf_csr1__DOT__mstatus;

}

void reg_display() {
	printf(ANSI_FMT(" name   value   name   value   name   value   name   value\n",ANSI_FG_BLUE));
	for(int i=0;i<32;i+=4){
		printf(ANSI_FMT(" %3s 0x%08x %3s 0x%08x %3s 0x%08x %3s 0x%08x\n",ANSI_FG_BLUE), regs[i], gpr(i), regs[i+1], gpr(i+1), regs[i+2], gpr(i+2), regs[i+3], gpr(i+3));
	}
}

word_t reg_str2val(const char *s, bool *success) {
	for(int i=0;i<REG_NUM;i++){
		if(!strcmp(regs[i],s)){ 
			*success=true;
			return gpr(i);
		}
	}
	*success=false;
	return 0;
}

bool difftest_checkregs(CPU_state* cpu_data_ref, vaddr_t pc){
    // reg_display();
	bool flag = true;
	for(int i=1;i<REG_NUM;i++){
		if(gpr(i) != cpu_data_ref->gpr[i]){
			printf(ANSI_FMT("--register [%s] should be %#010x,but exactly it's %#010x--\n",ANSI_FG_BLUE),regs[i],cpu_data_ref->gpr[i],gpr(i));
			flag = false;
		}
	}
    bool dif_cause = cpu_data_ref->mcause != cpu_data.mcause;
    bool dif_epc = cpu_data_ref->mepc != cpu_data.mepc;
    bool dif_vec = cpu_data_ref->mtvec != cpu_data.mtvec;
    if(dif_cause){
        printf(ANSI_FMT("--register [mcause] should be %#010x,but exactly it's %#010x--\n",ANSI_FG_BLUE),cpu_data_ref->mcause,cpu_data.mcause);
        flag = false;
    }
    if(dif_epc){
        printf(ANSI_FMT("--register [mepc  ] should be %#010x,but exactly it's %#010x--\n",ANSI_FG_BLUE),cpu_data_ref->mepc,cpu_data.mepc);
        flag = false;
    }
    if(dif_vec){
        printf(ANSI_FMT("--register [mtvec] should be %#010x,but exactly it's %#010x--\n",ANSI_FG_BLUE),cpu_data_ref->mtvec,cpu_data.mtvec);
        flag = false;
    }
	return flag;

}


/* EXEC */

static void single_inst_debug(){
	//TRACE
	IFDEF(CONFIG_ITRACE_COND,buffer_insert());
	IFDEF(CONFIG_FTRACE_COND,elf_call(cpu_data.pc,cpu_data.dnpc,cpu_data.inst));

	//watchpoint update
	if(launch_mode==SDB){	
		uint32_t new_result=0;
		WP* wp=check_wp(&new_result);
		if(wp!=NULL){
			cpu_status.state=STOP;
			printf(ANSI_FMT("Watchpoint change",ANSI_FG_GREEN) ":In No.%d,[%s],(0x%x==>0x%x)\n",wp->NO,wp->expr_s,wp->val,new_result);
			wp->val=new_result;
		}
	}
}


extern void clk_update();
void exec_once(){
    clk_update();
    if(need_debug) {
        IFDEF(CONFIG_STEP_INST,printf("%#010x:\t%#010x\n",cpu_data.pc,cpu_data.inst));
        get_cpu_reg();
        single_inst_debug();
        //DIFFTEST(REF = NEMU)
        IFDEF(CONFIG_DIFFTEST_COND,difftest_step(cpu_data.pc,cpu_data.dnpc,cpu_data.inst==0));
        inst_cnt++;
        need_debug = false;
    }
	

}



void device_update();
void exec(uint64_t n){
	
	switch(cpu_status.state){
		case TERMINATE:case ABORT:
			printf("Program execution has ended. To restart the program, exit NPC and run again.\n");
			
		return;

		default://STOP、ALIVE
			cpu_status.state=ALIVE;
		break;
	}



#ifdef CONFIG_ITRACE_COND
	log_write("\n[START INST LOGGING]\n");
	log_write("   %+7s\t\t%+15s\t\t\t%+10s\n","PC","INST","INST-HEX");
#endif
	for(;n>0 && cpu_status.state == ALIVE;n--){
        IFDEF(CONFIG_HOLD,if(cpu_data.pc==CONFIG_HOLD_PC) set_cpu_status(ABORT, cpu_data.pc, 1));
        // single_inst_debug();
		exec_once();
        device_update();
	}
#ifdef CONFIG_ITRACE_COND
	log_write("\n[TERMINATE INST LOGGING]\n");
#endif


	switch(cpu_status.state){
		case ALIVE:
			cpu_status.state = STOP;
		break;


		case ABORT:
		case TERMINATE:
			cpu_terminate();
		break;

		case QUIT:
			

		break;
	}
}


